package chatserver;

// This class handels connections; there will be as many instances of it as there will be connected users
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Connection implements Runnable {

    //TODO Сделать буфер равным макс. длине сообщения
    private byte buf[] = new byte[64 * 1024];
    private Socket cliSocket;
    private InputStream inpStream;
    private OutputStream outStream;
    private ChatServer server;
    private String handshake;
    private boolean connected = false;
    private InetAddress cliIA;
    private int cliPort;
    private String message;

    public Connection(Socket s, ChatServer server) {
        cliSocket = s;
        this.server = server;
        cliIA = cliSocket.getInetAddress();
        cliPort = cliSocket.getPort();
    }

    public boolean isConnected() {
        return connected;
    }

    public String getLastMessage() {
        return message;
    }

    public InetAddress getInetAddress() {
        return cliIA;
    }

    public int getPort() {
        return cliPort;
    }

    @Override
    public void run() {
        if (acceptClient()) {
            connected = true;
            server.someoneEntered();
            sendMessage("", ChatServer.CLEAR);
            sendMessage(server.getLastPosts(), ChatServer.POST);
            sendMessage("Connected to chat!", ChatServer.POST);
            System.out.println("Connected: " + cliIA.getHostName() + ":" + cliPort + " " + handshake);
            waitForMessage();
        } else {
            closeConnection();
        }
    }

    protected void sendMessage(String message, byte cmd) {
        try {
            byte[] mes = message.getBytes("utf-8");
            int mLength = mes.length;

            byte[] b = new byte[mLength + 3];

            int i = (((int) mLength & 0xff00) >> 8);
            int ii = (((int) mLength & 0xff));

            byte[] size = new byte[2];

            size[0] = (byte) i;
            size[1] = (byte) ii;

            b[0] = (byte) size[0];
            b[1] = (byte) size[1];
            b[2] = cmd;

            System.arraycopy(mes, 0, b, 3, mLength);
            outStream.write(b);

        } catch (Exception ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private boolean acceptClient() {
        try {
            inpStream = cliSocket.getInputStream();
            outStream = cliSocket.getOutputStream();

            byte[] b = readAll(inpStream, 61);
            handshake = new String(b, 0, b.length, "utf-8");

            int tStart = handshake.indexOf("token=", 0) + 6;
            int tEnd = tStart + 32;
            handshake = handshake.substring(tStart, tEnd).trim();
            //   Level one
            if (ChatServer.getHash(cliIA.getAddress()).equals(handshake)) {
                // Level two
                if (server.usesCaptcha()) {
                    sendMessage(ChatServer.DUMMY_CAPTCHA, ChatServer.CAPTCHA);
                    if (getMessage() != null) {
                        return true;
                    }
                } else {
                    return true;
                }
            }
            return false;
        } catch (Exception ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public String getMessage() throws Exception {
        int i = inpStream.read(buf, 0, buf.length);
        if (i == -1) {
            throw new EOFException();
        }
        String m = new String(buf, 0, i, "utf-8");
        return m;
    }

    private void waitForMessage() {
        while (connected) {
            try {
                message = getMessage();
                server.sendMessage(message, ChatServer.POST);
            } catch (Exception ex) {
                closeConnection();
                Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void closeConnection() {
        try {
            // Close the currently open objects
            inpStream.close();
            outStream.close();
            cliSocket.close();
            server.removeUser(this);
            connected = false;
        } catch (IOException ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static byte[] readAll(InputStream is, int size) throws IOException {
        byte[] data = new byte[size];
        int i = is.read(data, 0, size);

        while (i < size) {
            int j = size - i;
            j = is.read(data, i, j);
            i += j;
        }
        return data;
    }
}
